<!---
	Copyright 2010 Edward Trudeau, Jeremy Battle
    This software is freely available under the MIT License: http://www.opensource.org/licenses/mit-license.php
    http://ayc.googlecode.com
    
--->

<cfcomponent displayname="formUtils" output="no" hint="Utilities for handling dynamic form building and parameterization">
	
    <cffunction name="init" access="public" returntype="formUtils">
        
		<cfreturn this>
	</cffunction>
    
    <cffunction name="setFormVals" returntype="boolean" output="no" hint="Sets the default or current values of form fields">
    	<cfargument name="fieldArray" type="array" required="yes" hint="The Form.Fields array containing all metadata for the form fields" />
        <cfargument name="tableName" type="string" required="no" hint="Used for retrieving database information to load the form fields" />
        <cfargument name="dsn" type="string" required="no" hint="Used for retrieving database information to load the form fields" />
        <cfargument name="PKID" type="numeric" required="no" hint="The ID number of the database row to load. Required if tableName is supplied." />
        
        <cfset var fieldList = "" />
        <cfset var iii = "" />
        <cfset var nextField = "" />
        <cfset var dQry = "" />
        
        <cfif structKeyExists(arguments, "tableName") AND len(trim(arguments.tableName))>
			<cfif NOT structKeyExists(arguments, "PKID") OR NOT isValid('integer', arguments.PKID)>
        		<cfthrow type="formUtils.setFormVals.missingParameter" message="You must supply a Primary Key ID if you supply a table name to setFormVals."  detail="The table name submitted was #arguments.tableName#. Either omit the table name to load from defaults or specify the ID of the row to load." />
            </cfif>
            <cfif NOT structKeyExists(arguments, "dsn") OR NOT len(trim(arguments.dsn))>
        		<cfthrow type="formUtils.setFormVals.missingParameter" message="You must supply a dsn if you supply a table name to setFormVals."  detail="The table name submitted was #arguments.tableName#. Either omit the table name to load from defaults or specify the dsn of the database to use." />
            </cfif>
        </cfif>
        
        <cfif structKeyExists(arguments, "tableName") AND len(trim(arguments.tableName))>
        	<cfquery name="dQry" datasource="#arguments.dsn#">
            	SELECT * FROM #arguments.tableName# 
                WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.PKID#" />            
            </cfquery>
        </cfif>
        
        <cfloop from="1" to="#arrayLen(arguments.fieldArray)#" index="iii">
        	<cfloop list="#getFormFields(arguments.fieldArray[iii])#" index="nextField">
            	<cfif isQuery(dQry) AND dQry.recordCount>
                	<cfif NOT structKeyExists(form, "#nextField#")>
                		<cfset form["#nextField#"] = dQry["#arguments.fieldArray[iii]#"][1] />
                    </cfif>
                <cfelse>
                	<cfif NOT structKeyExists(form, "#nextField#")>
                		<cfset form["#nextField#"] = arguments.fieldArray[iii].default />
                    </cfif>
                </cfif>
            	
            </cfloop>
        </cfloop>
        
        
        <cfreturn true />
        
    </cffunction>
    
    <cffunction name="getAllFields" returntype="string" output="no" hint="Retrieves a list of form field names from the array of field metadata">
    	<cfargument name="fieldArray" type="array" required="yes" hint="The Form.Fields array containing all metadata for the form fields" />
    	
        <cfset var fieldList = "" />
        <cfset var iii = "" />
        
        <cfloop from="1" to="#arrayLen(arguments.fieldArray)#" index="iii">
			<cfset fieldList = listAppend(fieldList, getFormFields(arguments.FieldArray[iii])) />
        </cfloop>
        
        <cfreturn fieldList />
    </cffunction>
    
    <cffunction name="insert" returntype="struct" access="public" hint="inserts a row into the corresponding table for the submitted form fields">
		<cfargument name="fields" type="array" required="yes" hint="Array of field meta data structs; field 1 has the table and dsn information" />
		
		<cfset var result = structNew() />
		<cfset var nextTable = "" />
		<cfset var columnList = "" />
        <cfset var nextCol = "" />
		<cfset var fieldName = "" />
		<cfset var columnType = "" />
		<cfset var maxLength = "" />
        <cfset var qIns = "" />
        <cfset var qInsResult = "" />
        <cfset var iii = "" />
        <cfset var jjj = "" />
        
        <cfset result.success = false />
        <cfset result.catch = "Unknown error from formUtils.insert" />
        <cfset result.query = "" />
        <cfset result.FieldID = "" /> 
        <cfset result.content = "" />
		
		<cftry>
        	<cfif NOT arrayLen(arguments.fields)>
            	<cfthrow type="BadMetaDataArray" />
            </cfif>
            <cfloop from="1" to="#arrayLen(arguments.fields)#" index="iii">
            	<cfif arguments.fields[iii].fieldType EQ "Likert">
                	<cfloop from="1" to="#arrayLen(arguments.fields[iii].questions)#" index="jjj">
                    	<cfset columnList = listAppend(columnList, arguments.fields[iii].fieldName & "_#jjj#") />
                    </cfloop>
                <cfelseif listFindNoCase("checkbox,checkbox other", arguments.fields[iii].fieldType)>
                	<cfloop from="1" to="#arrayLen(arguments.fields[iii].options)#" index="jjj">
                    	<cfset columnList = listAppend(columnList, arguments.fields[iii].fieldName & "_#jjj#") />
                    </cfloop>
                    <cfif arguments.fields[iii].fieldType EQ "checkbox other">
                    	<cfset columnList = listAppend(columnList, arguments.fields[iii].fieldName & "_other") />
                    	<cfset columnList = listAppend(columnList, arguments.fields[iii].fieldName & "_otherVal") />
                    </cfif>
                <cfelseif arguments.fields[iii].fieldType EQ "radio other">
					<cfset columnList = listAppend(columnList, arguments.fields[iii].fieldName) />
                    <cfset columnList = listAppend(columnList, arguments.fields[iii].fieldName & "_otherVal") />
                <cfelse>
                	<cfset columnList = listAppend(columnList, arguments.fields[iii].fieldName) />
                </cfif>
            	
            </cfloop>
            <cfset nextTable = arguments.fields[1].meta.tableName />
			<cftransaction>
				
					<cfquery name="qIns" datasource="#arguments.fields[1].meta.dsn#" result="qInsResult">
						INSERT INTO #nextTable#
							(#columnList#)
						VALUES
							(<cfloop list="#columnList#" index="nextCol">
						<cfset fieldName = listGetAt(nextCol, 1, "_") />
                        <cfset iii = findField(arguments.fields, fieldName) />
                        <cfif iii EQ 0>
                        	<cfthrow type="FieldNotFound" message="#fieldName# not found in array; derived from column #nextCol#" />
                        </cfif>
						<cfset columnType = arguments.fields[iii].DataType />
						<cfset maxLength = arguments.fields[iii].DataMaxLength />

						<cfif len(trim(form["#nextCol#"]))>
                        	<cfif len(nextCol) GT 9 AND right(nextCol, 9) EQ "otherVal">
                            	<cfqueryparam 
                                      cfsqltype="varchar"
                                      maxlength="100"
                                      value="#trim(form['#nextCol#'])#" 
                                      null="no" />
                            <cfelse>
                            	<cfif isValid("integer", maxLength)>
                                    <cfqueryparam 
                                      cfsqltype="#getSQLTypeFromProp(columnType)#" 
                                      maxlength="#maxLength#" 
                                      value="#trim(form['#nextCol#'])#" 
                                      null="no" />
                                <cfelse>
                                    <cfqueryparam 
                                      cfsqltype="#getSQLTypeFromProp(columnType)#"
                                      value="#trim(form['#nextCol#'])#" 
                                      null="no" />
                                </cfif>
                            </cfif>
							
						<cfelse>
							<cfqueryparam cfsqltype="#getSQLTypeFromProp(columnType)#" value="" null="yes" />
						</cfif><cfif iii NEQ arrayLen(arguments.fields)>, </cfif>
							</cfloop>)
					</cfquery>
                    <cfset result.FieldID = qInsResult.IDENTITYCOL />
       				<cfset result.query = qInsResult.Sql />

			</cftransaction>
			
			<cfcatch type="database">
				<cfset result.catch = "#cfcatch.Message# & #cfcatch.Detail#" />
			</cfcatch>
            <cfcatch type="BadMetaDataArray">
            	<cfset result.catch = cfcatch.Message />
            </cfcatch>
            <cfcatch type="FieldNotFound">
            	<cfset result.catch = cfcatch.Message />
            </cfcatch>
            <cfcatch type="any">
            	<cfset result.catch = cfcatch.Message />
                <cfset result.content = columnList />
            </cfcatch>
		</cftry>
        
        <cfset result.success = true />
        
        <cfreturn result />
		
	</cffunction>
    
    <cffunction name="findField" access="private" hint="Finds the index in the field meta array that has the corresponding attribute" returntype="numeric">
    	<cfargument name="fields" type="array" required="yes" hint="The array of field metadata structures" />
        <cfargument name="fieldName" type="string" required="yes" hint="The name of the object to find" />
        
        <cfset var iii = "" />
        
        <cfloop from="1" to="#arrayLen(arguments.fields)#" index="iii">
        	<cfif arguments.fields[iii].fieldName EQ arguments.fieldName>
            	<cfreturn iii />
            </cfif>
        </cfloop>
        
        <cfreturn 0 />
    </cffunction>
    
    <cffunction name="getSQLTypeFromProp" access="private" hint="I return the correct sql type for a given property data type" returntype="string">
		<cfargument name="dataType" type="string" required="yes" hint="I am the data type as stored with the property" />
		
		<cfswitch expression="#arguments.dataType#">
			<cfcase value="int">
				<cfreturn "cf_sql_integer" />
			</cfcase>
			<cfcase value="tinyint">
				<cfreturn "CF_SQL_TINYINT" />
			</cfcase>
			<cfcase value="smallint">
				<cfreturn "CF_SQL_SMALLINT" />
			</cfcase>
			<cfcase value="bit">
				<cfreturn "CF_SQL_BIT" />
			</cfcase>
			<cfcase value="varchar">
				<cfreturn "CF_SQL_VARCHAR" />
			</cfcase>
			<cfcase value="text">
				<cfreturn "CF_SQL_LONGVARCHAR" />
			</cfcase>
			<cfcase value="datetime">
				<cfreturn "CF_SQL_TIMESTAMP" />
			</cfcase>
			<cfcase value="date">
				<cfreturn "CF_SQL_DATE" />
			</cfcase>
			<cfcase value="float">
				<cfreturn "CF_SQL_FLOAT" />
			</cfcase>
			<cfcase value="blob">
				<cfreturn "CF_SQL_BLOB" />
			</cfcase>
			<cfdefaultcase>
				<cfreturn "CF_SQL_CHAR" />
			</cfdefaultcase>
		
		</cfswitch>
	</cffunction>
    
    <cffunction name="getFormFields" returntype="string" output="no" hint="Retrieves the list of form fields for a given field metadata structure">
    	<cfargument name="FieldStruct" type="struct" required="yes" hint="The structure containing the field metadata" />
        
        <cfset var fieldList = "" />
        <cfset var jjj = "" />
        
        <cfif arguments.FieldStruct.FieldType EQ "Likert">
            <cfloop from="1" to="#arrayLen(arguments.FieldStruct.questions)#" index="jjj">
                <cfset fieldList = listAppend(fieldList, arguments.FieldStruct.fieldName&"_#jjj#") />
            </cfloop>
        <cfelseif  arguments.FieldStruct.FieldType EQ "Checkbox">
            <cfloop from="1" to="#arrayLen(arguments.FieldStruct.options)#" index="jjj">
                <cfset fieldList = listAppend(fieldList, arguments.FieldStruct.fieldName&"_#jjj#") />
            </cfloop>
        <cfelseif  arguments.FieldStruct.FieldType EQ "Checkbox Other">
            <cfloop from="1" to="#arrayLen(arguments.FieldStruct.options)#" index="jjj">
                <cfset fieldList = listAppend(fieldList, arguments.FieldStruct.fieldName&"_#jjj#") />
            </cfloop>
            <cfset fieldList = listAppend(fieldList,  arguments.FieldStruct.fieldName&"_other") />
            <cfset fieldList = listAppend(fieldList,  arguments.FieldStruct.fieldName&"_otherVal") />
        <cfelseif  arguments.FieldStruct.FieldType EQ "Radio Other">
            <cfset fieldList = arguments.FieldStruct.fieldName />
            <cfset fieldList = listAppend(fieldList,  arguments.FieldStruct.fieldName&"_otherVal") />
        <cfelse>
            <cfset fieldList = arguments.FieldStruct.fieldName />
        </cfif>
        
        <cfreturn fieldList />
    </cffunction>
    
    <cffunction name="renderField" returntype="string" access="public" output="no" hint="Returns HTML string for specified form object">
    	<cfargument name="FieldStruct" type="struct" required="yes" hint="The structure containing the field metadata" />
        <cfargument name="showOptions" type="string" required="no" default="label,field,container" hint="Comma separated list of what/how to display: label, field, container " />
        
        <cfset var outStr = "" />
        <cfset var iii = "" />
        <cfset var jjj = "" />
        
        <cfset var showContainer = yesNoFormat(listFindNoCase(arguments.showOptions, 'container')) />
        <cfset var showLabel = yesNoFormat(listFindNoCase(arguments.showOptions, 'label')) />
        <cfset var showField = yesNoFormat(listFindNoCase(arguments.showOptions, 'field')) />
        
        <cfif showContainer>
        	<cfset outStr &= '<div class="ctrlHolder">' />
        </cfif>
        
        <cfswitch expression="#arguments.FieldStruct.FieldType#">
        	<cfcase value="text">
            	<cfif showLabel>
            		<cfset outStr &= '<label for="' & arguments.FieldStruct.FieldName & '" class="' & arguments.FieldStruct.labelClass & '">' & arguments.FieldStruct.labelText & '</label>' />
                </cfif>
                <cfif showField>
                	<cfset outStr &= '<input type="text" id="' & arguments.FieldStruct.FieldName & '" name="' & arguments.FieldStruct.FieldName & '" value="' & form["#arguments.FieldStruct.FieldName#"] & '" class="' & arguments.FieldStruct.FieldClass & '" />' />
                </cfif>
            </cfcase>
            
            <cfcase value="hidden">
            	<cfif showLabel>
                	<cfset outStr &= '<label for="' & arguments.FieldStruct.FieldName & '" class="' & arguments.FieldStruct.labelClass & '">' & arguments.FieldStruct.labelText & '</label>' />
                </cfif>
                <cfif showField>
                	<cfset outStr &= '<input type="hidden" id="' & arguments.FieldStruct.FieldName & '" name="' & arguments.FieldStruct.FieldName & '" value="' & form["#arguments.FieldStruct.FieldName#"] & '" />' />
                </cfif>
            </cfcase>
            
            <cfcase value="select">
            	<cfif showLabel>
                	<cfset outStr &= '<label for="' & arguments.FieldStruct.FieldName & '" class="' & arguments.FieldStruct.labelClass & '">' & arguments.FieldStruct.labelText & '</label>' />
                </cfif>
                <cfif showField>
					<cfset outStr &= '<select id="' & arguments.FieldStruct.FieldName & '" name="' & arguments.FieldStruct.FieldName & '">' />
                    <cfloop from="1" to="#arrayLen(arguments.FieldStruct.Options)#" index="iii">
                        <cfset outStr &= '<option value="' & arguments.FieldStruct.Options[iii].value & '"' />
                        <cfif form["#arguments.FieldStruct.FieldName#"] EQ arguments.FieldStruct.Options[iii].value>
                            <cfset outStr &= ' selected' />
                        </cfif>
                        <cfset outStr &= '>' />
                        <cfif len(trim(arguments.FieldStruct.Options[iii].label))>
                            <cfset outStr &= arguments.FieldStruct.Options[iii].label />
                        <cfelse>
                            <cfset outStr &= arguments.FieldStruct.Options[iii].value />
                        </cfif>
                    </cfloop>
                    <cfset outStr &= '</select>' />
                </cfif>
            </cfcase>
            
            <cfcase value="radio,radio other">
            	<cfif showLabel>
            		<cfset outStr &= '<label for="' & arguments.FieldStruct.FieldName & '" class="' & arguments.FieldStruct.labelClass & '">' & arguments.FieldStruct.labelText & '</label>' />
                </cfif>
                <cfif showField>
                	<cfset outStr &= '<div' & iif(len(arguments.FieldStruct.FieldClass), de(' class="#arguments.FieldStruct.FieldClass#"'), de('')) & '>' />
                    <cfloop from="1" to="#arrayLen(arguments.FieldStruct.Options)#" index="iii">
                        <cfset outStr &= '<input type="radio" id="' & arguments.FieldStruct.FieldName & '" name="' & arguments.FieldStruct.FieldName & '" value="' & arguments.FieldStruct.Options[iii].value & '"' & iif(len(arguments.FieldStruct.FieldClass), de(' class="#arguments.FieldStruct.FieldClass#"'), de('')) />
                         <cfif form["#arguments.FieldStruct.FieldName#"] EQ arguments.FieldStruct.Options[iii].value>
                            <cfset outStr &= ' checked="checked"' />
                        </cfif>
                        <cfset outStr &= ' /><span class="optLabel"> ' />
                        <cfif len(trim(arguments.FieldStruct.Options[iii].label))>
                            <cfset outStr &= arguments.FieldStruct.Options[iii].label />
                        <cfelse>
                            <cfset outStr &= arguments.FieldStruct.Options[iii].value />
                        </cfif>
                        <cfset outStr &= '</span>' />
                    </cfloop>
                    <cfif arguments.FieldStruct.FieldType EQ 'radio other'>
                    	<cfset outStr &= '<input type="radio" id="' & arguments.FieldStruct.FieldName & '" name="' & arguments.FieldStruct.FieldName & '" value="0" class="' & arguments.FieldStruct.FieldClass & '"' />
                         <cfif form["#arguments.FieldStruct.FieldName#"] EQ '0'>
                            <cfset outStr &= ' checked="checked"' />
                        </cfif>
                        <cfset outStr &= ' /><span class="optLabel"> Other</span>' />
                        <cfset outStr &= '<input type="text" id="' & arguments.FieldStruct.FieldName & '_otherVal" name="' & arguments.FieldStruct.FieldName & '_otherVal" value="' & form["#arguments.FieldStruct.FieldName#_otherVal"] & '" class="' & arguments.FieldStruct.FieldClass & '" />' />
                    </cfif>
                    <cfset outStr &= '</div>' />
                </cfif>
            </cfcase>
            
            <cfcase value="checkbox,checkbox other">
            	<cfif showLabel>
            		<cfset outStr &= '<label for="' & arguments.FieldStruct.FieldName & '" class="' & arguments.FieldStruct.labelClass & '">' & arguments.FieldStruct.labelText & '</label>' />
                </cfif>
                <cfif showField>
                	<cfset outStr &= '<div' & iif(len(arguments.FieldStruct.FieldClass), de(' class="#arguments.FieldStruct.FieldClass#"'), de('')) & '>' />
                    <cfloop from="1" to="#arrayLen(arguments.FieldStruct.Options)#" index="iii">
                        <cfset outStr &= '<input type="checkbox" id="' & arguments.FieldStruct.FieldName & '_#iii#" name="' & arguments.FieldStruct.FieldName & '_#iii#" value="' & arguments.FieldStruct.Options[iii].value & '"' & iif(len(arguments.FieldStruct.FieldClass), de(' class="#arguments.FieldStruct.FieldClass#"'), de('')) />
                         <cfif form["#arguments.FieldStruct.FieldName#_#iii#"] EQ arguments.FieldStruct.Options[iii].value>
                            <cfset outStr &= ' checked="checked"' />
                        </cfif>
                        <cfset outStr &= ' /><span class="optLabel"> ' />
                        <cfif len(trim(arguments.FieldStruct.Options[iii].label))>
                            <cfset outStr &= arguments.FieldStruct.Options[iii].label />
                        <cfelse>
                            <cfset outStr &= arguments.FieldStruct.Options[iii].value />
                        </cfif>
                        <cfset outStr &= '</span>' />
                    </cfloop>
                    <cfif arguments.FieldStruct.FieldType EQ 'checkbox other'>
                    	<cfset outStr &= '<input type="checkbox" id="' & arguments.FieldStruct.FieldName & '_other" name="' & arguments.FieldStruct.FieldName & '_other" value="0" class="' & arguments.FieldStruct.FieldClass & '"' />
                         <cfif form["#arguments.FieldStruct.FieldName#_other"] EQ '0'>
                            <cfset outStr &= ' checked="checked"' />
                        </cfif>
                        <cfset outStr &= ' /><span class="optLabel"> Other</span>' />
                        <cfset outStr &= '<input type="text" id="' & arguments.FieldStruct.FieldName & '_otherVal" name="' & arguments.FieldStruct.FieldName & '_otherVal" value="' & form["#arguments.FieldStruct.FieldName#_otherVal"] & '" class="' & arguments.FieldStruct.FieldClass & '" />' />
                    </cfif>
                    <cfset outStr &= '</div>' />
                </cfif>
            </cfcase>
            
            <cfcase value="text area">
            	<cfif showLabel>
            		<cfset outStr &= '<label for="' & arguments.FieldStruct.FieldName & '" class="' & arguments.FieldStruct.labelClass & '">' & arguments.FieldStruct.labelText & '</label>' />
                </cfif>
                <cfif showField>
                	<cfset outStr &= '<textarea id="' & arguments.FieldStruct.FieldName & '" name="' & arguments.FieldStruct.FieldName & '" class="' & arguments.FieldStruct.FieldClass & '">' & form["#arguments.FieldStruct.FieldName#"] & '</textarea>' />
                </cfif>
            </cfcase>
            
            <cfcase value="likert">
            	<cfif showLabel>
            		<cfset outStr &= '<label for="' & arguments.FieldStruct.FieldName & '" class="' & arguments.FieldStruct.labelClass & '">' & arguments.FieldStruct.labelText & '</label>' />
                </cfif>
                <cfif showField>
                	<cfset outStr &= '<table class="likertTbl"><tr><th>&nbsp;</th>' />
                    <cfloop from="1" to="#arrayLen(arguments.FieldStruct.Options)#" index="iii">
                    	<cfset outStr &= '<th>' & iif(len(trim(arguments.FieldStruct.Options[iii].label)), de(arguments.FieldStruct.Options[iii].label), de(arguments.FieldStruct.Options[iii].value)) & '</th>' />
                    </cfloop>
                    <cfset outStr &= '</tr>' />
                    <cfloop from="1" to="#arrayLen(arguments.FieldStruct.Questions)#" index="jjj">
                    	<cfset outStr &= '<tr><td class="likertQuestion">' & arguments.FieldStruct.Questions[jjj] & '</td>' />
                        <cfloop from="1" to="#arrayLen(arguments.FieldStruct.Options)#" index="iii">
                        	<cfset outStr &= '<td align="center">' />
                            <cfset outStr &= '<input type="radio" id="' & arguments.FieldStruct.FieldName & '_#jjj#" name="' & arguments.FieldStruct.FieldName & '_#jjj#" value="' & arguments.FieldStruct.Options[iii].value & '" class="' & arguments.FieldStruct.FieldClass & '"' />
                             <cfif form["#arguments.FieldStruct.FieldName#_#jjj#"] EQ arguments.FieldStruct.Options[iii].value>
                                <cfset outStr &= ' checked="checked"' />
                            </cfif>
                            <cfset outStr &= ' />' />
                            <cfset outStr &= '</td>' />
                        </cfloop>
                        <cfset outStr &= '</tr>' />
                    </cfloop>
                    <cfset outStr &= '</table>' />
                </cfif>
            </cfcase>
            
            <cfdefaultcase>
            	<cfset outStr &= arguments.FieldStruct.LabelText />
            </cfdefaultcase>
        </cfswitch>
        
        
        
        <cfif showContainer>
        	<cfset outStr &= '</div>' />
        </cfif>
        
        <cfreturn outStr />
        
    </cffunction>
    
</cfcomponent>

